﻿using Devil;
using Excel;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using TableCore.Exceptions;
using TableCore.Plugin;

namespace TableCore
{
    public partial class GTFile
    {
        class MergeGenData : IExportData, IAsPrimary
        {
            public string AsPrimaryCol { get; private set; }

            public MergeGenData(Content content, ExportFileID file, IList<TabHeader> header, int rowId, DataRow row, string primary, EMergeType type)
            {
                ExportFile = file;
                Comment = Utils.GetString(row[COMMENT_COL]);
                ExportValues = new ExportValue[header.Count];
                AsPrimaryCol = type == EMergeType.Extract ? primary : null;
                int idindex = -1;
                int primaryIndex = -1;
                for (int i = 0; i < ExportValues.Length; i++)
                {
                    var h = header[i];
                    if (h.colIndex == -1)
                        continue;
                    var v = new ExportValue(h.name, h.type, Utils.GetString(row[h.colIndex]), h.comment);
                    ExportValues[i] = v;
                    if (v.name == primary)
                        primaryIndex = i;
                    if (StringUtil.EqualIgnoreCase(h.name, "id"))
                    {
                        idindex = i;
                        DataId = (int)h.type.Format(content, v.value, Utils.GetCellName(rowId, h.colIndex), Comment);
                    }
                }
                if(idindex != -1 && primaryIndex != -1 && primaryIndex != idindex && type != EMergeType.Default)
                {
                    var idvalue = ExportValues[idindex];
                    DataId = StringUtil.ToHash(ExportValues[primaryIndex].value);
                    idvalue = new ExportValue(idvalue.name, idvalue.type, Localization.ToString(DataId), idvalue.comment);
                    ExportValues[idindex] = idvalue;
                }
            }

            public int DataId { get; private set; }

            public string Comment { get; set; }

            public ExportFileID ExportFile { get; private set; }

            public ExportValue[] ExportValues { get; private set; }
        }

        public enum EMergeType
        {
            Default, // 平移
            Extract, // 展开
            Gather, // 合并
        }

        class MergeCmd : ICommand
        {
            GTFile gtfile;
            string file;
            string primary;
            EMergeType type;

            public MergeCmd(GTFile gt, string file, string primary, EMergeType type)
            {
                this.gtfile = gt;
                this.file = file;
                this.primary = primary;
                this.type = type;
            }

            public void Execute()
            {
                if (string.IsNullOrEmpty(file) || !File.Exists(file))
                    throw new FileNotFoundException("GTFile Can't open file: " + file, file);
                List<Content> tables = new List<Content>();
                using (var stream = new FileStream(file, FileMode.Open))
                {
                    IExcelDataReader reader = null;
                    try
                    {
                        if (file.EndsWith(".xls"))
                        {
                            reader = ExcelReaderFactory.CreateBinaryReader(stream);
                        }
                        else if (file.EndsWith(".xlsx"))
                        {
                            reader = ExcelReaderFactory.CreateOpenXmlReader(stream);
                        }
                        reader.IsFirstRowAsColumnNames = false;
                        while (reader.Read()) { }
                        var data = reader.AsDataSet();
                        stream.Close();
                        var sheets = data.Tables;
                        for (int i = 0; i < sheets.Count; i++) // (var table in data.Tables)
                        {
                            var table = sheets[i];
                            var cmod = new ClassModel(table.ToString());
                            cmod.GetProperties(gtfile.mValueFilters, table, START_ROW, START_COL);
                            var content = new Content(gtfile, file, data, cmod, i);
                            tables.Add(content);
                        }
                        lock (gtfile._lock)
                        {
                            gtfile.mDatas.Add(data);
                            gtfile.mContents.AddRange(tables);
                        }
                        //Debug.LogFormat("upload file: {0}", action.file);
                        var job = ParallelUtils.ScheduleParallel<Content>(ExportMergeData, tables, Factory.GetInstance().GetInitializeHandler());
                        //mExportCmd.Combine(job);
                        var clean = ParallelUtils.Schedule(gtfile.DisposeTable, data, false, job);
                        gtfile.mExportCmd.Combine(clean);
                    }
                    catch (Exception e)
                    {
                        Debug.LogFormat("open \"{0}\" error.\n{1}", file, e);
                    }

                }
            }
            void ExportMergeData(Content content)
            {
                var table = content.Data.Tables[content.TableIndex];
                var path = Utils.CalculateRelativePath(gtfile.mWorkFolder, content.File);
                string pack;
                if (string.IsNullOrEmpty(path))
                {
                    pack = Utils.GetFileName(content.File, false);
                    if (type == EMergeType.Gather)
                        path = Path.Combine(gtfile.mDataOutputFolder, string.Format("{0:x}.xlsx", StringUtil.ToHash(pack)));
                    else if (type == EMergeType.Extract)
                        path = Path.Combine(gtfile.mDataOutputFolder, Utils.GetCell(table, 1, 0));
                    else
                        path = Path.Combine(gtfile.mDataOutputFolder, path);
                }
                else
                {
                    pack = path.Replace('\\', '/');
                    if (type == EMergeType.Gather)
                        path = Path.Combine(gtfile.mDataOutputFolder, string.Format("{0:x}.xlsx", StringUtil.ToHash(pack)));
                    else if (type == EMergeType.Extract)
                        path = Path.Combine(gtfile.mDataOutputFolder, Utils.GetCell(table, 1, 0));
                    else
                        path = Path.Combine(gtfile.mDataOutputFolder, path);
                }
                if (type == EMergeType.Extract && !File.Exists(path))
                    throw new IOException("file not found, extract file to " + path);
                Debug.LogFormat("file \"{0}\" will merge to \"{1}\"", content.File, path);

                var cmod = content.Cmodel;
                ExportFileID exportfile = new ExportFileID(path, cmod.ClassName);
                var errorType = StringUtil.GetBuilder();
                for (int i = 0; i < cmod.PropertyCount; i++)
                {
                    var p = cmod.GetProperty(i);
                    if (p.Ignore)
                        continue;
                    if (!p.GenType.IsValidType())
                    {
                        if (errorType.Length > 0)
                            errorType.Append(',');
                        errorType.Append(p.GenType.Name);
                    }
                }
                if (errorType.Length > 0)
                    throw new TypeNotDefinedException(content, StringUtil.ReturnRelease(errorType));

                var cols = table.Columns.Count;
                List<TabHeader> headers = new List<TabHeader>(cmod.PropertyCount);
                var headRow = table.Rows[START_ROW];
                var typeRow = table.Rows[START_ROW + 1];
                var commentRow = table.Rows[START_ROW + 2];
                for (int c = START_COL; c < cols; c++)
                {
                    var pname = Utils.GetString(headRow[c]);
                    if (string.IsNullOrEmpty(pname))
                        break;
                    var typename = Utils.GetString(typeRow[c]);
                    var type = Factory.GetInstance().GetGenType(typename);
                    if (type == null || !type.IsValidType())
                        throw new TypeNotDefinedException(content, typename);
                    var isId = StringUtil.EqualIgnoreCase(pname, "id");
                    if (!isId && !ValueFilter.PassValue(pname, gtfile.mValueFilters))
                        continue;
                    var head = new TabHeader(type, pname, Utils.GetString(commentRow[c]));
                    head.colIndex = c;
                    if (headers.Count > 0 && isId)
                        headers.Insert(0, head);
                    else
                        headers.Add(head);
                }
                if (headers.Count == 0)
                    return;
                int rows = table.Rows.Count;
                for (int i = START_ROW + 3; i < rows; i++)
                {
                    var row = table.Rows[i];
                    if (string.IsNullOrEmpty(Utils.GetString(row[START_COL])))
                        break;
                    var exp = new MergeGenData(content, exportfile, headers, i, row, primary, type);
                    gtfile.ExportOutput(pack, exp, false);
                }
            }

        }

        // 上传（打开）待合并的文件
        public void UploadMergeFile(string file, string primary, EMergeType type, bool useFilter)
        {
            if (string.IsNullOrEmpty(file))
                return;
            lock (_lock)
            {
                if (mStopUpload)
                    throw new Exception("已经结束文件上传，请确保在 FinishUpload 之前完成上传工作.");
                InitFilters();
                var pname = !useFilter || mFilters == null || mFilters.Count == 0 ? file : file.Replace('\\', '/');
                var pass = mExportData && (!useFilter || mFilters == null || FileFilter.PassDataFile(pname, mFilters));
                if (!pass)
                    return;
                var path = Path.GetFullPath(file);
                if (!mFiles.Add(path))
                    return;
                var cmd = new MergeCmd(this, file, primary, type);

                mUploadCmd = ParallelUtils.Schedule(cmd, false, mUploadCmd);
            }
        }

    }
}
